/*
    SEL - Simple DirectMedia Layer Extension Library
    Copyright (C) 2002 Matej Knopp <knopp@users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "tokenizer.h"
#include "../base/debug.h"
#include "../base/misc.h"

namespace SEL
{

Tokenizer::Tokenizer (RWops *rwops, const std::string & filename)
{
	m_file = rwops;
	m_filename = filename;
	m_token_type = TOKEN_INVALID;
	m_token_value = "";
	m_ok = true;
	m_next_token_type = TOKEN_INVALID;
	m_next_token_value = "";
	m_have_next_token = false;
	m_line = m_last_line = 0;
	m_operators = ",=;{}()";
	m_eof = false;
	m_next_token_res = false;
}

Tokenizer::~Tokenizer ()
{
	if (!m_files.empty ())
	{
		warning ("Some files left unclosed - closing!");
		while (!m_files.empty ())
		{
			RWops *ops = m_files.top ();
			m_files.pop ();
			del (ops);
		}
	}
	if (m_file)
		del (m_file);
}

void 
Tokenizer::setOperators (const std::string & operators)
{
	m_operators = operators;
}

bool 
Tokenizer::isOperator (char ch) const
{
	std::string::const_iterator it = m_operators.begin ();
	while (it != m_operators.end ())
	{
		if ((*it) == ch)
			return true;
		++it;			
	}
	return false;
}

bool
Tokenizer::isWhiteSpace (char ch)
{
	return (ch == 32 || // space
		ch == 9  || // tab
		ch == 13 || // new line
		ch == 10);  // CR
}

void 
Tokenizer::skipWhiteSpaces ()
{
	char c = getChar ();

	while (!eof () && ok () && (Tokenizer::isWhiteSpace (c) 
	       || isComment (c)))
	{
		if (isComment (c))
			skipLine ();
		if (c == '\n')
			m_line ++;
		c = getChar ();
	}

	// go back one character
	putChar ();	
}

void 
Tokenizer::skipLine ()
{
	char c = getChar ();
	while (!eof () && ok () && c != '\n')
	{
		c = getChar ();		
	}
	if (!eof () && ok ())
		m_line ++;
}

char 
Tokenizer::getChar ()
{
	char ch = m_file->getChar ();
	if (m_file->status () & RWops::STAT_EOF)
		m_eof = true;
	if (m_file->status () & RWops::STAT_ERROR)
		m_ok = false;	
	return ch;
}

void 
Tokenizer::putChar ()
{
	m_file->seek (-1, SEEK_CUR);
}

bool 
Tokenizer::pushFile (RWops *ops, const std::string & filename)
{
	for (unsigned int i = 0; i < m_filenames.size (); i++)
	{
		if (m_filenames[i] == filename)
			return false;
	}
	m_files.push (m_file);
	m_file = ops;
	m_filenames.push_back (m_filename);
	m_filename = filename;
	m_lines.push (m_line);
	m_line = 0;
	m_last_lines.push (m_last_line);
	m_last_line = 0;
	return true;
}
	
bool 
Tokenizer::popFile ()
{
	if (m_files.empty ())
		return false;
	del (m_file);
	m_file = m_files.top ();
	m_files.pop ();
	m_filename = m_filenames[m_filenames.size () - 1];
	m_filenames.pop_back ();
	m_line = m_lines.top ();
	m_lines.pop ();
	m_last_line = m_last_lines.top ();
	m_last_lines.pop ();
	return true;
}

bool 
Tokenizer::processNextToken (int &type, std::string &value)
{
	type = TOKEN_INVALID;
	value = "";

	// Skip the white spaces (if any)
	while (1)
	{
		skipWhiteSpaces ();

		if (!ok ()) // unknown error - leave
			return false;
			
		if (eof ()) // end of file
		{
			if (popFile ())
			{
				m_eof = false;
				continue; // have another file
			}
			else
				return false;
		}
		else // ok
			break;
	}
		
	m_last_line = m_line;

	char c = getChar ();
	
	if (c == '"') // we have quoted string
	{
		c = getChar ();
		while (!eof () && ok ())
		{
			if (c == '\\')
			{
				c = getChar ();
				if (eof () || !ok ())
					break;
				if (c == 'n')
					value += '\n';
				else if (c == 'r')
					value += '\r';
				else if (c == 'f')
					value += '\f';
				else if (c == 't')
					value += '\t';
				else if (c == 'b')
					value += '\b';
				else if (c == 'a')
					value += '\a';
				else if (c == '\\')
					value += '\\';
				else if (c == '"')
					value += '"';
				else if (c == '\n')
					;
				else if (c == '\r') // M$-DOS file ?
				{
					c = getChar ();
					if (eof () || !ok ())
						break;					
					if ( c != '\n' )
						putChar ();
				}
				else if (c == 'x' || c == 'X') // hexa code
				{
					char code[3];
					code [0] = getChar ();
					code [1] = getChar ();
					code [2] = 0;
					if (eof () || !ok ()) 
						break;
					if (Tokenizer::isHexInteger
					    (code, false))
					{
						unsigned int icode;
						sscanf (code, "%x", &icode);
						value += (char)icode;
					}
				}
				else if (c == '0' || c == '1' || c == '2')
				{
					char code[4];
					code [0] = c;
					code [1] = getChar ();
					code [2] = getChar ();
					code [3] = 0;
					if (eof () || !ok ()) 
						break;
					if (Tokenizer::isDecInteger (code))
					{
						unsigned int icode;
						icode = atoi (code);
						if (icode <= 255)
							value += (char)icode;
					}
				}
			}
			else if (c == '\r') // M$-DOS EOL ?
			{
				char c2 = getChar ();
				if (eof () || !ok ()) 
					break;
				
				if (c2 != '\n') // no
					putChar ();
				else c = '\n';
				value += c;
			}
			else if (c == '"')
			{
				type = TOKEN_STRING;
				return true;
			}
			else
			{
				value += c;
			}
			if (c == '\n')
				m_line ++;
			c = getChar ();
		}
		if (eof ()) 
		{
			char msg[4096];
			sprintf (msg, "Unclosed string starting at line %i.",
				 m_last_line + 1);
			m_error = msg;
			m_ok = false;
			return false;
		}
		if (!ok ())
			return false;
	} // if (c == '"') // we have quoted string
	
	while (ok () && !eof ())
	{
		if (Tokenizer::isWhiteSpace (c))
		{
			type = TOKEN_STRING;
			putChar ();
			return true;
		}
		if (Tokenizer::isComment (c))
		{
			type = TOKEN_STRING;
			putChar ();
			return true;
		}
		else if (isOperator (c))
		{
			if (value.length () == 0)
			{
				value = c;
				type = TOKEN_OPERATOR;
			} 
			else
			{
				type = TOKEN_STRING;
				putChar ();
			}
			return true;
		}
		value += c;
		c = getChar ();				
	}
	if (!ok ())
		return false;	
	putChar (); // we've had an EOF - put it back
	type = TOKEN_STRING;
	m_eof = false;
	return true;
}

bool 
Tokenizer::isDecInteger (const char *what)
{
	unsigned int i = 0;
	unsigned int len = strlen (what);

	while (Tokenizer::isWhiteSpace (what[i])) 
		i++;

	while (Tokenizer::isWhiteSpace (what[len-1]))
		len--;
	
	if (what[i] == '-' || what[i] == '+')
		i++;
	
	if (what[i] == 0) 
		return false;

	for (; i < len; i++)
	{
		if (what[i] < '0' || what[i] > '9') 
		{
			return false;
		}
	}
	return true;
}

bool 
Tokenizer::isHexInteger (const char *what, bool have_prefix)
{
	unsigned int i = 0;
	unsigned int len = strlen (what);

	while (Tokenizer::isWhiteSpace (what[i])) 
		i++;

	while (Tokenizer::isWhiteSpace (what[len-1]))
		len--;
	
	if (have_prefix && strlen(what) < 3+i) 
		return false;
	
	if (what[0] == 0)
		return false;
	
	if (have_prefix)
	{
		if (what[i] != '0' || (what[i+1] != 'x' && what[i+1] != 'X'))
			return false;
		i+=2;
	};
	
	for (; i < len; i++)
	{
		if ( (what[i] < '0' || what[i] > '9') &&
		     (what[i] < 'A' || what[i] > 'F') &&
		     (what[i] < 'a' || what[i] > 'f') )
		{
			return false;
		}	
	}
	return true;
}


bool 
Tokenizer::isFloat (const char *what)
{
	unsigned int i = 0;
	unsigned int len = strlen (what);
	bool was_fp = false;
	bool was_e = false;

	while (Tokenizer::isWhiteSpace (what[i])) 
		i++;

	while (Tokenizer::isWhiteSpace (what[len-1]))
		len--;

	if (what[i] == '-' || what[i] == '+')
		i++;
	
	if (what[i] == 0) 
		return false;
	
	for (; i < len; i++)
	{
		if (what[i] == '.') {
			if (was_fp)
				return false;
			was_fp = true;
			continue;
		}
		if (what[i] == 'e' || what[i] == 'E')
		{
			if (was_e)
				return false;
			was_e = true;
			if (what[i+1] == '+' || what[i+1] == '-')
				i++;
			continue;
		}
		if (what[i] < '0' || what[i] > '9')
			return false;
	}	
	return true;
}

bool
Tokenizer::nextToken ()
{
	if (m_have_next_token)
	{
		m_have_next_token = false;
		m_token_type = m_next_token_type;
		m_token_value = m_next_token_value;
		m_next_token_type = TOKEN_INVALID;
		m_next_token_value = "";
		m_eof = m_next_eof;
		m_ok = m_next_ok;
		m_error = m_next_error;
		if (m_next_token_res)
			m_tokens_processed++;
		return m_next_token_res;
	}
	bool ret = processNextToken (m_token_type, m_token_value);
	if (ret)
		m_tokens_processed++;
	return ret;
}

bool
Tokenizer::peekNextToken ()
{
	bool old_ok = m_ok;
	bool old_eof = m_eof;
	std::string old_error = m_error;
	m_next_token_res = processNextToken (m_next_token_type,
					     m_next_token_value);
	m_next_ok = m_ok;
	m_ok = old_ok;
	m_next_eof = m_eof;
	m_eof = old_eof;
	m_next_error = m_error;
	m_error = old_error;
	m_have_next_token = true;
	return m_next_token_res;
}

} // namespace SEL
